package com.qianxin.ids.idam.am.authn.factors.cass.upsso.refreshToken;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.gitee.starblues.realize.PluginUtils;
import com.qianxin.ids.idam.framework.cache.common.CacheConfigImpl;
import com.qianxin.ids.idam.framework.cache.common.CacheServiceFactory;
import com.qianxin.ids.idam.framework.cache.iface.ICache;
import com.qianxin.ids.idam.framework.common.utils.ServerModeHelper;
import com.qianxin.ids.idam.framework.common.utils.ServerSide;
import com.qianxin.ids.idam.sdk.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@Slf4j
@Component
public class RefreshTokenCache {
    @Autowired
    private  PluginUtils pluginUtils;
    private ServerModeHelper serverModeHelper;
    private CacheServiceFactory cacheServiceFactory;
    private volatile ICache<String, JSONObject> tokenCache;
    private volatile ICache<String, String> adminLogOutCache;
    private final AtomicBoolean isInitialized = new AtomicBoolean(false);
    private final CompletableFuture<Boolean> initFuture = new CompletableFuture<>();
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private static final int INITIAL_RETRY_TIME = 20; // 初始重试间隔（秒）
    private static final int MIN_RETRY_TIME = 10;    // 最小重试间隔（秒）
    private  int currentRetryTime = INITIAL_RETRY_TIME; // 当前重试间隔

    /**
     *  构造一个按过期时间排序的队列
     */
    ConcurrentUniquePriorityQueue expiryQueue;



    @PostConstruct
    private void init() {
        cacheServiceFactory = pluginUtils.getMainBean(CacheServiceFactory.class);
        serverModeHelper = pluginUtils.getMainBean(ServerModeHelper.class);
        expiryQueue = new ConcurrentUniquePriorityQueue();
    }

    /**
     * @return
     */
    public CompletableFuture<Boolean> cacheInit() {
        CompletableFuture.runAsync(() -> {
            while (!isInitialized.get()) {
                try {
                    // 1. 检查 cacheServiceFactory 是否注入
                    if (cacheServiceFactory == null) {
                        cacheServiceFactory = pluginUtils.getMainBean(CacheServiceFactory.class);
                        if (cacheServiceFactory == null) {
                            log.info("RefreshTokenTask ==>CacheServiceFactory 未注入，等待主应用初始化...");
                            TimeUnit.SECONDS.sleep(currentRetryTime);
                            continue;
                        }
                    }

                    // 2. 检查 CacheService 是否可用
                    try {
                        cacheServiceFactory.getCacheService();
                        initializeTokenCache();
                        isInitialized.set(true);
                        initFuture.complete(true); // 标记初始化成功
                        scheduler.shutdown(); // 成功后关闭调度器
                        log.info("RefreshTokenTask ==>缓存初始化完成！");
                        break;
                    } catch (Exception e) {
                        log.info("RefreshTokenTask ==>CacheService 暂不可用，等待 {} 秒后重试...", currentRetryTime);
                        // 指数退避：逐步减少等待时间，但不超过 MIN_RETRY_TIME
                        currentRetryTime = Math.max(currentRetryTime / 2, MIN_RETRY_TIME);
                        TimeUnit.SECONDS.sleep(currentRetryTime);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warn("RefreshTokenTask ==>缓存初始化被中断，强制关闭", e);
                    initFuture.completeExceptionally(e); // 标记初始化失败
                    scheduler.shutdownNow(); // 中断时强制关闭
                    break;
                } catch (Exception e) {
                    log.warn("RefreshTokenTask ==>缓存初始化异常", e);
                    initFuture.completeExceptionally(e); // 标记初始化失败
                }
            }
        }, scheduler);

        return initFuture;
    }

    private void initializeTokenCache() {
        CacheConfigImpl config = new CacheConfigImpl();
        config.setKeyType(String.class);
        config.setName("refreshTokenCache");
        config.setValueType(String.class);
        config.setCacheMode("STRING");
        config.setTimeUnit(TimeUnit.SECONDS);
        tokenCache = cacheServiceFactory.getCacheService().getCache(config);

        // 消费时清理。
        CacheConfigImpl adminLogOutConfig = new CacheConfigImpl();
        adminLogOutConfig.setKeyType(String.class);
        adminLogOutConfig.setName("adminLogOutCache");
        adminLogOutConfig.setValueType(String.class);
        adminLogOutConfig.setCacheMode("STRING");
        adminLogOutConfig.setTimeUnit(TimeUnit.SECONDS);
        adminLogOutCache = cacheServiceFactory.getCacheService().getCache(adminLogOutConfig);


        // 清空缓存。
        tokenCache.clear();
        log.info("RefreshTokenTask ==>缓存初始化完成");
    }

    @PreDestroy
    public void destroy() {
        // 确保资源清理
        if (!scheduler.isShutdown()) {
            scheduler.shutdownNow();
        }
        tokenCache.clear();
    }


    /**
     * todo 可以保存RefreshEntity类。
     * 但在获取缓存时会报错：Caused by: org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve type id 'com.qianxin.ids.idam.am.authn.factors.cass.upsso.refreshToken.RefreshEntity' as a subtype of `java.lang.Object`: no such class found
     *  at [Source: (byte[])"["com.qianxin.ids.idam.am.authn.factors.cass.upsso.refreshToken.RefreshEntity",{"contextId":"sid_AnXjgxjt64uX4cuUtP5KoPKVIIKowRoz","userId":null,"token":"bd0edad2-2946-48d6-8880-a958f8e9a4f8","session":"123456","instanceId":"bd0edad2-2946-48d6-8880-a958f8e9a4f8","tokenExpireTime":null,"expireTime":0,"refreshToken":null,"refreshWindowMinutes":5}]"; line: 1, column: 71]; nested exception is
     * @return
     */
    public List<RefreshEntity> getAll() {
        List<JSONObject> values = Optional.ofNullable(tokenCache.getValues())
                .orElse(Collections.emptyList());
        List<RefreshEntity> collect = values.parallelStream()
                .map(jsonStr -> JSON.toJavaObject(jsonStr, RefreshEntity.class))  // 转换为JSON字符串)
                .filter(Objects::nonNull)       // 过滤掉转换失败的条目
                .collect(Collectors.toList());
        return collect;
    }


    public void put(RefreshEntity entity){
        if (entity == null || entity.getContextId() == null) {
            log.warn("RefreshTokenTask ==>Attempting to put invalid entity: {}", entity);
            return;
        }
        try {
            // 使用Fastjson序列化并保持字段顺序
            JSONObject json = new JSONObject(true);  // 保持put顺序
            json.putAll(JSON.parseObject(JSON.toJSONString(entity, SerializerFeature.SortField)));
            tokenCache.put(entity.getContextId(), json);
            expiryQueue.offer(entity);
        } catch (Exception e) {
            log.error("RefreshTokenTask ==>Failed to serialize RefreshEntity: {}", entity, e);
        }
    }

    public void putAll(List<RefreshEntity> all){
        if (all == null || all.isEmpty()) {
            log.debug("RefreshTokenTask ==>Attempting to put empty collection");
            return;
        }
        all.stream()
                .filter(Objects::nonNull)
                .filter(e -> e.getContextId() != null)
                .forEach(entity ->{
                    try {
                        JSONObject json = (JSONObject) JSONObject.toJSON(entity);
                        tokenCache.put(entity.getContextId(), json);
                        expiryQueue.offer(entity);
                    } catch (Exception e) {
                        log.warn("RefreshTokenTask ==>Serialization failure for {}", entity.getContextId(), e);
                    }
                });
    }

    public ConcurrentUniquePriorityQueue getPriorityQueue(){
        return expiryQueue;
    }

    public void remove(String id) {
        // admin侧调用下线时 , 由于优先队列是本地的，runtime\admin不互通。会导致runtime侧不会删除该上下文。
        // 情形较少。可以做数据对齐 ： 利用redis。
        if (serverModeHelper.getServerSide() == ServerSide.ADMIN){
            adminLogOutCache.put(id, id);
        }
        expiryQueue.remove(JSONObject.toJavaObject(tokenCache.get(id), RefreshEntity.class));
        tokenCache.remove(id);
    }

    /**
     * 取出Admin侧的contextId
     * @return
     */
    public String popAdminLogOutCache(String contextId){
        String id = adminLogOutCache.get(contextId);
        if (StringUtils.isNotEmpty(id)){
            adminLogOutCache.remove(id);
        }
        return id;
    }



}
